﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Threading;
using System.Windows;

namespace Foundation
{
    /// <summary>
    /// Instance Manager.
    /// </summary>
    public static class InstanceManager
    {
        private static readonly string EventName;
        private static readonly string MutexName;
        private static readonly string MappingName;
        private static readonly Process CurrentProcess = Process.GetCurrentProcess();
        private static EventWaitHandle _eventWaitHandle;

        static InstanceManager()
        {
            var appName = Application.Current.GetType().Assembly.ManifestModule.ScopeName;
            var windowsIdentity = System.Security.Principal.WindowsIdentity.GetCurrent();
            var keyUserName = windowsIdentity != null && windowsIdentity.User != null
                                  ? windowsIdentity.User.ToString()
                                  : String.Empty;
            // Be careful! Max 260 chars!
            EventName = string.Format(@"{0}{1}-event", appName, keyUserName);
            MutexName = string.Format(@"{0}{1}-mutex", appName, keyUserName);
            MappingName = string.Format(@"{0}{1}-mapping", appName, keyUserName);
        }

        public delegate void ArgumetProcessor(IEnumerable<string> args);

        public static ArgumetProcessor OnReciveArguments;

        /// <summary>
        /// Determines whether this instance [can create instance] the specified args.
        /// </summary>
        /// <param name="args">The args.</param>
        /// <returns>
        ///   <c>true</c> if this instance [can create instance] the specified args; otherwise, <c>false</c>.
        /// </returns>
        public static bool CanCreateInstance(string[] args)
        {
            try
            {
                _eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, EventName);
                using (var mapping = MemoryMappedFile.OpenExisting(MappingName))
                {
                    SafeSendArgs(mapping, args);
                }

                _eventWaitHandle.Set();
            }
            catch (FileNotFoundException)
            {
                new Thread(ArgumentReceiver) {IsBackground = true}.Start();
                return true;
            }
            catch (Exception exception)
            {
                MessageBox.Show(exception.Message);
            }

            return false;
        }

        /// <summary>
        /// Determines whether [is single instance].
        /// </summary>
        /// <returns>
        ///   <c>true</c> if [is single instance]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsSingleInstance()
        {
            var remoteProcesses = Process.GetProcessesByName(CurrentProcess.ProcessName);
            var isSingleInstance = remoteProcesses.All(remoteProcess =>
                                                       remoteProcess.StartInfo.FileName !=
                                                       CurrentProcess.StartInfo.FileName);
            return isSingleInstance;
        }

        private static void ArgumentReceiver()
        {
            using (var mapping = MemoryMappedFile.CreateNew(MappingName, 4*1024))
            {
                try
                {
                    while (_eventWaitHandle.WaitOne())
                    {
                        var args = SafeReceiveArgs(mapping);

                        if (OnReciveArguments != null)
                            OnReciveArguments(args);
                    }
                }
                catch (Exception exception)
                {
                    MessageBox.Show(exception.Message);
                }
            }
        }

        private static IEnumerable<string> SafeReceiveArgs(MemoryMappedFile mapping)
        {
            using (var mutex = new Mutex(false, MutexName))
            {
                mutex.WaitOne();
                using (var reader = new StreamReader(mapping.CreateViewStream()))
                {
                    var args = new List<string>();
                    for (var arg = reader.ReadLine(); !reader.EndOfStream; arg = reader.ReadLine())
                    {
                        args.Add(arg);
                    }

                    return args;
                }
            }
        }

        private static void SafeSendArgs(MemoryMappedFile mapping, IEnumerable<string> args)
        {
            using (var mutex = new Mutex(false, MutexName))
            {
                mutex.WaitOne();
                using (var writer = new StreamWriter(mapping.CreateViewStream()))
                {
                    foreach (var arg in args)
                    {
                        writer.WriteLine(arg);
                    }
                }
            }
        }
    }
}